#include "Loader.h"

#include "ObjMesh.h"
#include "Object.h"
#include "Material.h"
#include "Texture.h"

#include <QHash>
#include <QDir>
#include <QtXml>

using namespace std;

namespace Loader
{

QString m_libraryPath;
QHash<QString, ObjMesh*> m_meshMap;
QHash<QString, Texture*> m_texMap;


QString getObjectPath(FurnitureType type, const QString &objName)
{
    QString result;

    switch(type)
    {
        case BUILDING:
        {
            result = m_libraryPath + "buildings/" + objName + ".obj";
            break;
        }
        case CHAIR:
        {
            result = m_libraryPath + "chairs/" + objName + ".obj";
            break;
        }
        case DESK:
        {
            result = m_libraryPath + "desks/" + objName + ".obj";
            break;
        }
        case GENERAL:
        {
            result = m_libraryPath + "general/" + objName + ".obj";
            break;
        }
    }

    return result;
}

QString getTexturePath(FurnitureType type, const QString &objName)
{
    QString result;

    switch(type)
    {
        case BUILDING:
        {
            result = m_libraryPath + "buildings/" + objName + ".png";
            break;
        }
        case CHAIR:
        {
            result = m_libraryPath + "chairs/" + objName + ".png";
            break;
        }
        case DESK:
        {
            result = m_libraryPath + "desks/" + objName + ".png";
            break;
        }
        case GENERAL:
        {
            result = m_libraryPath + "general/" + objName + ".png";
            break;
        }
    }

    if(!QFile::exists(result))
        return "";

    return result;
}


void setLibrary(const QString &libraryPath)
{
    QFileInfo info(libraryPath);
    if(!info.exists() || !info.isDir())
        qCritical() << "Library path \"" << m_libraryPath << "\" not found!";

    m_libraryPath = info.path();
    if(!m_libraryPath.endsWith(QDir::separator()))
        m_libraryPath.append(QDir::separator());

    info.setFile(m_libraryPath + "library.xml");
    if(!info.exists())
        qCritical() << "Library path " << m_libraryPath << " doesn't have a \"library.xml\" file. But it must have.";
}

QString getLibraryPath()
{
    return m_libraryPath;
}

QList<FurnitureDescription> getLibraryInfoList()
{
    QList<FurnitureDescription> result;

    QFile libraryFile(m_libraryPath + "library.xml");

    if(!libraryFile.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        qCritical() << "File " << libraryFile.fileName() << " couldn't be opened!";
        return result;
    }

    QDomDocument libraryDoc;
    QString errorStr;
    int errorLine;
    int errorColumn;
    if (!libraryDoc.setContent(&libraryFile, false, &errorStr, &errorLine, &errorColumn))
    {
        qCritical() << "Error: Parse error at line " << errorLine << ", "
                  << "column " << errorColumn << ": "
                  << qPrintable(errorStr);
        return result;
    }

    QDomElement root = libraryDoc.documentElement();
    if (root.tagName() != "library")
    {
        qCritical() << libraryFile.fileName() << " isn't a library file!";
        return result;
    }

    QDomElement element;
    element = root.firstChildElement("chairs");
    if(!element.isNull())
    {
        QDomElement chairElement = element.firstChildElement("object");
        while(!chairElement.isNull())
        {
            FurnitureDescription description;
            description.name = chairElement.attribute("name");
            description.type = CHAIR;
            description.preview = QImage(m_libraryPath + "chairs/" + description.name + "_preview.png");

            result.append(description);

            chairElement = chairElement.nextSiblingElement("object");
        }
    }

    element = root.firstChildElement("desks");
    if(!element.isNull())
    {
        QDomElement chairElement = element.firstChildElement("object");
        while(!chairElement.isNull())
        {
            FurnitureDescription description;
            description.name = chairElement.attribute("name");
            description.type = DESK;
            description.preview = QImage(m_libraryPath + "desks/" + description.name + "_preview.png");

            result.append(description);

            chairElement = chairElement.nextSiblingElement("object");
        }
    }

    element = root.firstChildElement("general");
    if(!element.isNull())
    {
        QDomElement chairElement = element.firstChildElement("object");
        while(!chairElement.isNull())
        {
            FurnitureDescription description;
            description.name = chairElement.attribute("name");
            description.type = GENERAL;
            description.preview = QImage(m_libraryPath + "general/" + description.name + "_preview.png");

            result.append(description);

            chairElement = chairElement.nextSiblingElement("object");
        }
    }

    return result;
}

ObjMesh* loadMesh(FurnitureType type, const QString &name, Texture **texReturn)
{
    QString path = getObjectPath(type, name);
    ObjMesh *mesh = NULL;

    if(m_meshMap.contains(path))
        mesh = m_meshMap.value(path);
    else
    {
        mesh = new ObjMesh(getObjectPath(type, name).toStdString(), type);
        m_meshMap.insert(path, mesh);
    }

    if(texReturn)
    {
        if(mesh->hasTextureCoords())
        {
            QString texPath = getTexturePath(type, name);
            if(texPath != "")
            {
                if(m_texMap.contains(texPath))
                    *texReturn = m_texMap.value(texPath);
                else
                {
                    *texReturn = new Texture(texPath.toStdString());
                    m_texMap.insert(texPath, *texReturn);
                }
            }
            else
                *texReturn = NULL;
        }
        else
            *texReturn = NULL;
    }

    return mesh;
}

Object* loadObject(FurnitureType type, const QString &name)
{
    ObjMesh *mesh = NULL;
    Texture *tex = NULL;
    mesh = loadMesh(type, name, &tex);

    Material* material = new Material();
    if(tex)
        material->setTexture(tex);

    Object *obj = new Object(mesh, material);
    obj->setName(name.toStdString());
    return obj;
}

}
